package com.swak.boot.config.jdbc.database;

import static com.swak.boot.Application.APP_LOGGER;

import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.StringUtils;

import com.swak.boot.config.customizer.SyncDataSourceOptionsCustomizer;
import com.swak.boot.config.jdbc.database.DynamicDataSourceAutoConfiguration.DynamicDataSourceCondition;
import com.swak.boot.config.jdbc.database.DynamicDataSourceProperties.Config;
import com.swak.boot.config.jdbc.database.DynamicDataSourceProperties.Dynamic;
import com.swak.persistence.datasource.DynamicDataSource;
import com.swak.persistence.datasource.DynamicDataSourceAspect;
import com.swak.persistence.datasource.MultiDataSource;
import com.swak.utils.Maps;
import com.zaxxer.hikari.HikariDataSource;

/**
 * 配置 Hikari
 * 
 * @author lifeng
 */
@Conditional(DynamicDataSourceCondition.class)
@ConditionalOnClass({ HikariDataSource.class })
@ConditionalOnMissingBean(DataSource.class)
@EnableConfigurationProperties(DynamicDataSourceProperties.class)
public class DynamicDataSourceAutoConfiguration {

	private final Dynamic properties;

	public DynamicDataSourceAutoConfiguration(DynamicDataSourceProperties properties) {
		this.properties = properties.getDynamic();
		APP_LOGGER.debug("Loading Dynamic DataSource");
	}

	@Bean
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public DataSource dynamicDataSource(ObjectProvider<List<SyncDataSourceOptionsCustomizer>> customizersProvider) {
		Map<String, DataSource> dataSources = this.createDataSources(customizersProvider);
		DataSource priary = dataSources.get(properties.getPrimary());
		DataSource dynamicDataSource = new DynamicDataSource(priary, (Map) dataSources);
		return dynamicDataSource;
	}

	private Map<String, DataSource> createDataSources(
			ObjectProvider<List<SyncDataSourceOptionsCustomizer>> customizersProvider) {
		Map<String, DataSource> dataSources = Maps.newHashMap();
		List<Config> configs = properties.getDatasources();
		for (int i = 0; i < configs.size(); i++) {

			// 配置
			Config config = configs.get(i);

			// 创建数据源
			DataSource source = this.createDataSource(config.getGroup() + ":" + i, config, customizersProvider);

			// 是否已经存在
			DataSource exists = dataSources.get(config.getGroup());
			if (exists != null && !(exists instanceof MultiDataSource)) {
				MultiDataSource multiDataSource = new MultiDataSource();
				multiDataSource.addDataSource(source);
				multiDataSource.addDataSource(exists);
			} else if (exists != null && exists instanceof MultiDataSource) {
				((MultiDataSource) exists).addDataSource(source);
			} else {
				dataSources.put(config.getGroup(), source);
			}
		}
		return dataSources;
	}

	private DataSource createDataSource(String name, DataSourceProperties properties,
			ObjectProvider<List<SyncDataSourceOptionsCustomizer>> customizersProvider) {
		return new HikariDataSourceAutoConfiguration(name).hikariDataSource(properties, customizersProvider);
	}

	@Bean
	public DynamicDataSourceAspect dynamicDataSourceAspect() {
		return new DynamicDataSourceAspect();
	}

	static class DynamicDataSourceCondition extends SpringBootCondition {

		private static final String DATASOURCE_URL_PROPERTY = "spring.datasource.url";

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
			ConditionMessage.Builder message = ConditionMessage.forCondition("DynamicDataSource");
			if (!hasDataSourceUrlProperty(context)) {
				return ConditionOutcome.match();
			}
			return ConditionOutcome.noMatch(message.because(DATASOURCE_URL_PROPERTY + " is set"));
		}

		private boolean hasDataSourceUrlProperty(ConditionContext context) {
			Environment environment = context.getEnvironment();
			if (environment.containsProperty(DATASOURCE_URL_PROPERTY)) {
				try {
					return StringUtils.hasText(environment.getProperty(DATASOURCE_URL_PROPERTY));
				} catch (IllegalArgumentException ex) {
					// Ignore unresolvable placeholder errors
				}
			}
			return false;
		}
	}
}
